/* Copyright (C) 2007-2010 STS Soft

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; version 2 of the License.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA */

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using STSdb.Files;

namespace STSdb.Data
{
    public class Scheme : XTable<Locator, SchemeRecord>
    {
        public StorageEngine Engine { get; private set; }

        public Scheme(RawFile rawFile, ulong offset, StorageEngine engine)
            :base(rawFile, offset)
        {
            if (engine == null)
                throw new ArgumentNullException("engine");

            Engine = engine;
        }

        public void Register<TKey, TRecord>(Locator locator, XTable<TKey, TRecord> table)
        {
            this[locator] = new SchemeRecord(table.RawFile.Handle, table.Offset, table.GetType());
        }

        public void Register(Locator locator, XTable table)
        {
            this[locator] = new SchemeRecord(table.RawFile.Handle, table.Offset, table.xTableType);
        }

        public XTable<TKey, TRecord> CreateXTable<TKey, TRecord>(Locator locator, RawFile rawFile)
        {
            XTable<TKey, TRecord> table = new XTable<TKey, TRecord>(rawFile, rawFile.Length);
            Register<TKey, TRecord>(locator, table);

            return table;
        }

        public XTable CreateXTable(Locator locator, Type keyType, Type recordType)
        {
            RawFile rawFile = Engine.FileSystem.CreateFile();
            Type openType = typeof(XTable<,>);
            Type closeType = openType.MakeGenericType(keyType, recordType);
            object table = Activator.CreateInstance(closeType, new object[] { rawFile, rawFile.Length });

            XTable xTable = new XTable(table);
            Register(locator, xTable);

            return xTable;
        }

        public XTable<TKey, TRecord> CreateXTable<TKey, TRecord>(Locator locator)
        {
            return CreateXTable<TKey, TRecord>(locator, Engine.FileSystem.CreateFile());
        }

        public XTable<TKey, TRecord> OpenXTable<TKey, TRecord>(Locator locator)
        {
            SchemeRecord record;
            if (!TryGet(locator, out record))
                return null;

            if (record.Type != typeof(XTable<TKey, TRecord>))
                throw new InvalidCastException("Incompatible types.");

            RawFile rawFile = Engine.FileSystem.OpenFile(record.Handle);
            return new XTable<TKey, TRecord>(rawFile, record.Offset);
        }

        public XTable OpenXTable(Locator locator)
        {
            SchemeRecord record;
            if (!TryGet(locator, out record))
                return null;

            RawFile rawFile = Engine.FileSystem.OpenFile(record.Handle);
            Type openType = typeof(XTable<,>);
            Type closeType = openType.MakeGenericType(record.Type.GetGenericArguments());
            object table = Activator.CreateInstance(closeType, new object[] { rawFile, record.Offset });
            return new XTable(table);
        }

        public XTable<TKey, TRecord> CreateOrOpenXTable<TKey, TRecord>(Locator locator)
        {
            SchemeRecord record;
            if (!TryGet(locator, out record))
                return CreateXTable<TKey, TRecord>(locator);

            return OpenXTable<TKey, TRecord>(locator);
        }

        public XTable CreateOrOpenXTable(Locator locator, Type keyType, Type recordType)
        {
            SchemeRecord record;
            if (!TryGet(locator, out record))
                return CreateXTable(locator, keyType, recordType);

            return OpenXTable(locator);
        }

        public void CopyTables(Scheme dstScheme)
        {
            foreach (var schemeRow in this) //this.Forward()
            {
                Type tableType = schemeRow.Record.Type;
                Type keyType = tableType.GetGenericArguments()[0];
                Type recordType = tableType.GetGenericArguments()[1];

                XTable srcTable = this.OpenXTable(schemeRow.Key);
                XTable dstTable = dstScheme.CreateOrOpenXTable(schemeRow.Key, keyType, recordType);
                dstTable.RecordPersist = srcTable.RecordPersist;

                foreach (var row in srcTable.Forward())
                    dstTable[row.Key] = row.Record;

                dstTable.Commit();
            }
        }
    }

    public class SchemeRecord
    {
        /// <summary>
        /// Container RawFile Handle
        /// </summary>
        public ulong Handle { get; set; }
        /// <summary>
        /// Position of XTable in RawFile
        /// </summary>
        public ulong Offset { get; set; }
        /// <summary>
        /// Type of the XTable(TKey, TRecord)
        /// </summary>
        public Type Type { get; set; }

        public SchemeRecord()
        {
        }

        public SchemeRecord(ulong handle, ulong offset, Type type)
        {
            Handle = handle;
            Offset = offset;
            Type = type;
        }
    }
}
